package com.tianji.learning.utils;

import com.alibaba.fastjson.JSON;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.JsonUtils;
import com.tianji.common.utils.StringUtils;
import com.tianji.learning.domain.po.LearningLesson;
import com.tianji.learning.domain.po.LearningRecord;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.service.ILearningLessonService;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 *  ① 添加播放记录到Redis，并添加一个延迟检测任务到DelayQueue
 *  ② 查询Redis缓存中的指定小节的播放记录
 *  ③ 删除Redis缓存中的指定小节的播放记录
 *  ④ 异步执行DelayQueue中的延迟检测任务，检测播放进度是否变化，如果无变化则写入数据库
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class LearningRecordDelayTaskHandler {
    private final ILearningLessonService lessonService;
    private final LearningRecordMapper recordMapper;
    private final DelayQueue<DelayTask<RecordTaskData>> delayQueue = new DelayQueue<>();//声明延迟队列
    private final StringRedisTemplate redisTemplate;
    private final static String RECORD_KEY_TEMPLATE = "learning:record:%s";
    public static final String SECTION_ID_S = "sectionId-%s";
    private static volatile boolean begin = true;
    private final static ThreadPoolExecutor executor = new ThreadPoolExecutor(32,40,60, TimeUnit.SECONDS,new LinkedBlockingDeque<>(10));
    @PostConstruct
    public void init(){
        //解释：类初始化 属性注入值后启动一个异步任务（开启一个新线程），该任务会调用 handleDelayTask 方法
        CompletableFuture.runAsync(this::handleDelayTask);
    }

    @PreDestroy
    public void destroy() {
        begin=false;
        log.debug("延迟任务停止执行！");
    }

    /**
     * 方法一 添加播放记录到Redis，并添加一个延迟检测任务到DelayQueue
     * 所需参数 lessonId -》 sectionId -- monment、finished、id
     */
    public void addDelayTask(LearningRecord record) {
        // 1.添加数据到Redis缓存
        writeRecordCache(record);
        // 2.添加延迟任务到DelayQueue
        delayQueue.add(new DelayTask<>(new RecordTaskData(record), Duration.ofSeconds(20)));
    }

    public void writeRecordCache(LearningRecord record) {
        log.debug("更新学习记录的缓存数据");
        try {
            // 1.添加记录到redis
            String key = String.format(RECORD_KEY_TEMPLATE, record.getLessonId());
            String hashKey = String.format(SECTION_ID_S, record.getSectionId());
            String value = JSON.toJSONString(new RecordCacheData(record));
            redisTemplate.opsForHash().put(key,hashKey,value);
            redisTemplate.expire(key,Duration.ofMinutes(1));//设置一分钟的过期时间 我们每20秒消费一次
        } catch (Exception e) {
            log.error("更新学习记录缓存异常", e);
            throw new BizIllegalException("更新学习记录缓存异常");
        }
    }

    /**
     * 方法二：查询Redis缓存中的指定小节的播放记录
     */
    public LearningRecord readRecordCacheData(Long lessonId, Long sectionId) {
        log.info("查询学习记录缓存数据");
        Object cacheData = null;
        try {
            String key = String.format(RECORD_KEY_TEMPLATE, lessonId);
            String hashKey = String.format(SECTION_ID_S, sectionId);
            cacheData = redisTemplate.opsForHash().get(key, hashKey);
            if (cacheData == null) {
                return null;
            }
        } catch (Exception e) {
            throw new BizIllegalException("查询学习记录缓存异常");
        }
        return JsonUtils.toBean(cacheData.toString(), LearningRecord.class);
    }

    /**
     * 方法三：删除Redis缓存中的指定小节的播放记录
     */
    public void deleteRecordCacheData(Long lessonId, Long sectionId) {
        log.info("删除学习记录缓存数据");
        try {
            String key = String.format(RECORD_KEY_TEMPLATE, lessonId);
            String hashKey = String.format(SECTION_ID_S, sectionId);
            redisTemplate.opsForHash().delete(key, hashKey);
            log.info("删除学习记录缓存数据，key:{},hashKey:{}",key,hashKey);
        }catch (Exception e) {
            throw new BizIllegalException("删除学习记录缓存数据异常");
        }
    }

    /**
     * 方法四：异步执行DelayQueue中的延迟检测任务，检测播放进度是否变化，如果无变化则写入数据库
     * 无变化代表这用户没有继续播放视频了 20秒检查一次 前端15秒提交一次播放时长
     */
    public void handleDelayTask() {
        while (begin) {
            try {
                //1、获取任务
                DelayTask<RecordTaskData> task = delayQueue.take();
                //2、使用线程池执行任务
                executor.submit(() -> {
                    RecordTaskData data = task.getData();//任务中的数据
                    //2、查看redis缓存
                    LearningRecord record = readRecordCacheData(data.getLessonId(), data.getSectionId());
                    log.info("时间{}，任务数据：{}====缓存数据:{}",LocalDateTime.now(),data,record);

                    if (record==null){
                        return;
                    }
                    //3、redis缓存存在 比较时间是否相同
                    if (!record.getMoment().equals(data.getMoment())){
                        //不相同
                        return;
                    }
                    //4、用户没有继续看视频了 写入数据库 更新学习记录 课表最近学习小节 时间

                    log.info("进行了合并写，写入数据库的操作");
                    // 4.1.更新学习记录的moment
                    record.setFinished(null);
                    recordMapper.updateById(record);
                    // 4.2.更新课表最近学习信息
                    LearningLesson lesson = new LearningLesson();
                    lesson.setId(data.getLessonId());
                    lesson.setLatestSectionId(data.getSectionId());
                    lesson.setLatestLearnTime(LocalDateTime.now());
                    lessonService.updateById(lesson);
                });
            } catch (Exception e) {
                log.error("处理延迟任务发生异常", e);
                throw new RuntimeException(e);
            }
        }
    }
}
@Data//要缓存到redis的数据
class RecordCacheData {
    private Long id;
    private Integer moment;
    private Boolean finished;

    public RecordCacheData(LearningRecord record) {
        this.id = record.getId();
        this.moment = record.getMoment();
        this.finished = record.getFinished();
    }
}
@Data//任务对象
class RecordTaskData {
    private Integer moment;
    private Long lessonId;
    private Long sectionId;
    
    public RecordTaskData(LearningRecord record) {
        this.lessonId = record.getLessonId();
        this.moment = record.getMoment();
        this.sectionId = record.getSectionId();
    }
}

@Data
class DelayTask<T> implements Delayed {
    private T data;
    private Long deadlineNanos;

    public DelayTask(T data, Duration delayTime) {
        this.data = data;
        this.deadlineNanos = System.nanoTime() + delayTime.toNanos();
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(Math.max(0, deadlineNanos - System.nanoTime()), TimeUnit.NANOSECONDS);
    }


    @Override
    public int compareTo(Delayed o) {
        long b = this.getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
        return Long.compare(b,0);
    }
}
